package epsofts.Neutron;

import java.util.Random;
import java.util.Set;

/**
 * @version 1.0
 * @created 14-sept.-2006 22:48:08
 */
public class JoueurMiniMaxAB implements IJoueur
{
	public static final String	LABEL						= "CPU (MiniMaxAB)";

	public static final Boolean	ENABLED						= true;

	public static final float	INFINI						= Float.MAX_VALUE - 10;

	private static final Random	random						= new Random();

	/**
	 * Profondeur d'analyse.
	 */
	private short				m_iProfondeurMax			= 3;

	/**
	 * Nombre de g�n�rations al�atoires.
	 */
	private short				m_iGenerationsAleatoires	= 2;

	/**
	 * Tableaux des meilleurs coups, autant d'�l�ments que de profondeur.
	 */
	private Coup[]				m_meilleursCoups			= null;

	/**
	 * Thread AlphaBeta.
	 */
	private Thread				m_thAlphaBeta				= null;
	
	/**
	 * Nombre de coups évalués.
	 */
	private int					m_nbCoupsEvalues			= 0;

	/**
	 * Foncteur impl�mentant la fonction d'�valuation.
	 */
	private final IEvalueur		m_Evalueur;

	public JoueurMiniMaxAB(final short i_profondeur_max, final IEvalueur i_evalueur, final short i_GenerationsAleatoires)
	{
		m_iProfondeurMax = i_profondeur_max;
		m_iGenerationsAleatoires = i_GenerationsAleatoires;
		m_Evalueur = i_evalueur;
	}

	/**
	 * Donne le tour de jeu au joueur (pour le plateau donn�) et attend une proposition de mouvement (Coup).
	 * 
	 * @param plateau
	 */
	public Coup Jouer(final Plateau plateau) throws InterruptedException
	{
		if (m_thAlphaBeta != null) throw new RuntimeException("Joueur d�j� en train de jouer");

		if (plateau.getGeneration() < m_iGenerationsAleatoires)
		{
			Set<Coup> coups = plateau.listerCoups();
			Coup coup = null;
			eCase estTerminal = eCase.NEUTRON;

			do
			{
				coup = (Coup) coups.toArray()[random.nextInt(coups.size())];
				estTerminal = plateau.jouerCoup(coup).testerPlateauTerminal(true); // TODO ici ?
			} while (estTerminal != eCase.VIDE);
			return coup;
		}

		m_meilleursCoups = new Coup[m_iProfondeurMax];

		m_thAlphaBeta = new Thread(new Runnable()
		{
			public void run()
			{
				try
				{
					AlphaBeta((short) 0, plateau, +INFINI, -INFINI);
				}
				catch(InterruptedException e)
				{
					System.out.println("MiniMax interrupted");
				}
			}
		});

		m_thAlphaBeta.setName("Thread Alpha-Beta");
		m_thAlphaBeta.setPriority(Thread.MAX_PRIORITY);
		m_nbCoupsEvalues = 0;
		long time = System.currentTimeMillis();
		m_thAlphaBeta.start();

		try
		{
			m_thAlphaBeta.join();
			time = System.currentTimeMillis() - time;
			
		}
		catch (InterruptedException e)
		{
			m_thAlphaBeta.interrupt();
			
			m_thAlphaBeta.join();
			m_thAlphaBeta = null;
			throw e;
		}
		m_thAlphaBeta = null;

		System.out.format("JoueurMiniMaxAB] nb de coups évalués : %d; profonfeur : %d; %.2fs\n", m_nbCoupsEvalues, m_iProfondeurMax, ((float) time/1000));
		return m_meilleursCoups[0];
	}

	/**
	 * Doit tnterrompre le tour du joueur si celui-ci �tait en train de jouer.
	 */
	public void Stopper()
	{
		//TODO: ne sert à rien (déjà interrompu à ce stade)
		System.out.println("Stopper JoueurMiniMaxAB (nothing to do)");
		//m_thAlphaBeta.interrupt();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see Neutron.IJoueur#OnJeuFini(Neutron.eCase)
	 */
	public void OnJeuFini(eCase i_vainqueur)
	{
		if ((m_thAlphaBeta != null) && m_thAlphaBeta.isAlive() && !m_thAlphaBeta.isInterrupted())
		{
			Stopper();
		}
	}

	/**
	 * Teste d'abord si le plateau est terminal, sinon apelle l'Evalueur.
	 * 
	 * @param plateau
	 *            Plateau � �valuer.
	 * @return score du plateau (de -INFINI � +INFINI).
	 */
	private float Evaluer(final Plateau plateau)
	{
		eCase base = plateau.testerPlateauTerminal(false); // TODO ici ?
		switch (base)
		{
		case ROUGE:
			return +INFINI;
		case BLEUE:
			return -INFINI;
		default:
			return m_Evalueur.evalueurPlateau(plateau);
		}
	}

	/**
	 * D�termine le coup � jouer suivant l'algorithme MiniMaxAlphaBeta avec l'�valueur et les param�tres donn�s. Fonction r�cursive.
	 * 
	 * @param profondeur_max
	 *            Profondeur max de l'analyse.
	 * @param profondeur
	 *            Profondeur courante de l'analyse.
	 * @param plateau
	 *            Plateau � �tudier.
	 * @param beta
	 *            Valeur courante de la borne Beta.
	 * @param alpha
	 *            Valeur courante de la borne Alpha.
	 * @param meilleur_coup
	 *            Valeur du meilleur coup pour le noeud courant.
	 * @throws InterruptedException 
	 */
	public float AlphaBeta(short profondeur, final Plateau plateau, final float beta, final float alpha) throws InterruptedException
	{
		if (m_thAlphaBeta.isInterrupted())
		{
			throw new InterruptedException();
		}
		
		++profondeur;
		float score = Evaluer(plateau);
		++m_nbCoupsEvalues;

		if ((score >= INFINI) || (score <= -INFINI) || (profondeur > m_iProfondeurMax))
		{
			return score;
		}
		else
		{
			float score_fils;
			final Set<Coup> coups;

			float Alpha = -INFINI;
			float Beta = +INFINI;

			if (plateau.getTour() == eCase.BLEUE) // Noeud MIN
			{
				coups = plateau.listerCoups();
				for (Coup coup : coups)
				{
					Plateau coupJoue = plateau.jouerCoup(coup);					
					score_fils = AlphaBeta(profondeur, coupJoue, ((Beta < beta) ? Beta : beta), alpha);
					Beta = ((Beta < score_fils) ? Beta : score_fils);

					if ((profondeur == 1) && (Beta == score_fils))
					{
						m_meilleursCoups[0] = coup;
					}

					if (alpha >= Beta) // Coupure alpha
					{
						--profondeur;
						return Beta;
					}
				}

				--profondeur;
				return Beta;
			}
			else
			// Noeud MAX
			{
				coups = plateau.listerCoups();
				for (Coup coup : coups)
				{
					Plateau coupJoue = plateau.jouerCoup(coup);
					score_fils = AlphaBeta(profondeur, coupJoue, beta, ((alpha > Alpha) ? alpha : Alpha));
					Alpha = ((score_fils > Alpha) ? score_fils : Alpha);

					if ((profondeur == 1) && (Alpha == score_fils))
					{
						m_meilleursCoups[0] = coup;
					}

					if (Alpha >= beta) // Coupure beta
					{
						--profondeur;
						return Alpha;
					}
				}

				--profondeur;
				return Alpha;
			}
		}

	}

}
